/*
 *MIT License
 *
 *Copyright (c) 2019 chenshuai cs4380@163.com
 *
 *Permission is hereby granted, free of charge, to any person obtaining a copy
 *of this software and associated documentation files (the "Software"), to deal
 *in the Software without restriction, including without limitation the rights
 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *copies of the Software, and to permit persons to whom the Software is
 *furnished to do so, subject to the following conditions:
 *
 *The above copyright notice and this permission notice shall be included in all
 *copies or substantial portions of the Software.
 *
 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *SOFTWARE.
 */

package com.cs.cslc.auth.service;

import com.cs.cslc.admin.dto.RoleMenuAuthDTO;
import com.cs.cslc.admin.entity.BaseUser;
import com.cs.cslc.admin.service.BaseDeptUserBiz;
import com.cs.cslc.admin.service.BaseUserBiz;
import com.cs.cslc.admin.service.SysRoleAuthorizationBiz;
import com.cs.cslc.auth.bean.JWTUserBean;
import com.cs.cslc.auth.utils.JWTUtil;
import com.cs.cslc.common.constant.CommonConstant;
import com.cs.cslc.common.constant.InitialCapacityConstant;
import com.cs.cslc.common.constant.RedisKeysConstant;
import com.cs.cslc.common.context.BaseContextHandler;
import com.cs.cslc.common.enums.AuthenticationEnums;
import com.cs.cslc.common.exception.LoginException;
import com.cs.cslc.common.util.BeanUtil;
import com.cs.cslc.common.util.DateTimeUtils;
import com.cs.cslc.common.util.Sha256Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * UserService 用户业务处理.
 *
 * @author cs4380 https://gitee.com/xhbug_cs4380  cs4380@163.com
 * @version 1.0
 * @since 2019-10-02 18:22
 */
@Component
@Slf4j
public class UserService {
    /**
     * 用户token失效时间（单位：分钟）
     */
    @Value("${auth.token.expire-time:120}")
    private int expireTime;

    @Autowired
    private BaseUserBiz baseUserBiz;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SysRoleAuthorizationBiz roleAuthorizationBiz;

    @Autowired
    private BaseDeptUserBiz baseDeptUserBiz;
    /**
     * 放行地址
     */
    private static final List<String> URLS = new ArrayList<>();
    /**
     * 权限基础配置
     */
    private static final Map<String, List<RoleMenuAuthDTO>> ROLE_BTN_AUTH = new HashMap<>();

    UserService() {
        if (BeanUtil.isEmpty(URLS)) {
            URLS.add("/api/auth/login");
            URLS.add("/api/auth/login/verifyCode");
            URLS.add("/api/auth/logout");

            // api相关文档
            URLS.add("/api/doc.html");
            URLS.add("/api/v3/api-docs/**");
            URLS.add("/api/favicon.ico");
            URLS.add("/api/webjars/css/**");
            URLS.add("/api/webjars/js/**");
            URLS.add("/api/swagger-resources/**");
        }
    }

    /**
     * 用户登陆，获取token信息
     * <p>
     * 暂时只支持单机登陆，不支持多机登陆
     * </p>
     *
     * @param account  用户账号
     * @param password 用户密码
     * @return token信息
     */
    public String login(String account, String password) {
        // 获取系统用户信息
        BaseUser baseUser = baseUserBiz.getBaseUserByAccount(account);

        // Base64解码,sha256密码加密
        password = Sha256Util.getSHA256(new String(Base64.getDecoder().decode(password)));
        // 验证是否存在用户名和密码
        this.verify(baseUser, password);

        // 获取用户认证信息
        JWTUserBean userBean = this.getUserBean(baseUser);

        long expire = DateTimeUtils.minutesToMilliseconds(expireTime);
        // 生成用户token
        String token = JWTUtil.sign(userBean, JWTUtil.getSecret(userBean), expire);

        // 缓存用户token，并设置过期时间
        try {
            stringRedisTemplate.opsForValue().set(RedisKeysConstant.USER_TOKENS + account, token, expire, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException("系统内部错误，请联系管理员！");
        }
        return token;
    }

    /**
     * 获取用户认证信息
     * <p>
     * 用户基本信息角色、部门等信息
     * </p>
     *
     * @param baseUser 系统用户信息
     * @return 用户基本信息角色、部门等信息
     */
    public JWTUserBean getUserBean(BaseUser baseUser) {
        // 封装认证的用户信息
        JWTUserBean jwtUser = new JWTUserBean();
        if (null == baseUser) {
            return jwtUser;
        }
        jwtUser.setUserId(baseUser.getId());
        jwtUser.setAccount(baseUser.getAccount());
        jwtUser.setName(baseUser.getUserName());
        jwtUser.setIsSuperAdmin(baseUser.getIsSuperAdmin());
        jwtUser.setTenantId(baseUser.getTenantId());
        // 添加用户部门、角色
        List<String> depts = baseDeptUserBiz.getDeptCodesByUserId(baseUser.getId());
        jwtUser.setDepts(depts);
        // 用户角色
        List<String> roles = baseUserBiz.getUserRolesByUserId(baseUser.getId());
        jwtUser.setRoles(roles);
        return jwtUser;
    }

    /**
     * 获取用户认证信息
     *
     * @param account 用户账号
     * @return 用户认证信息
     */
    public JWTUserBean getUserBean(String account) {
        // 获取系统用户信息
        BaseUser baseUser = baseUserBiz.getBaseUserByAccount(account);
        return this.getUserBean(baseUser);
    }

    /**
     * 验证用户登陆信息和权限信息
     *
     * @param baseUser 系统用户信息
     * @param password 登陆时密码
     */
    private void verify(BaseUser baseUser, String password) {
        // 账号是否存在，密码是否正确
        if (null == baseUser || !password.equals(baseUser.getPassword())) {
            throw new LoginException();
        }
        // 用户是否禁用
        if (CommonConstant.IS_FALSE.equals(baseUser.getIsDisabled())) {
            throw new LoginException(AuthenticationEnums.DISABLED_ACCOUNT.getValue());
        }
    }

    /**
     * 是否放行RUL地址
     *
     * @param requestUrl 请求url地址
     * @return true：允许放行,false：访问限制
     */
    public boolean isExcludeURL(String requestUrl) {
        // 默认不放行
        boolean isExclude = false;
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        // 匹配放行地址
        for (String url : URLS) {
            if (antPathMatcher.match(url, requestUrl)) {
                isExclude = true;
                break;
            }
        }
        return isExclude;
    }

    /**
     * 是否有权限获取访问当前请求
     *
     * @param requestUrl 请求地址
     * @param method     请求方式
     * @return false：权限不足，true允许登陆
     */
    public boolean isPermitted(String requestUrl, String method) {
        // 判断当前用户是否超级管理员，超级管理员跳过验证
        Integer isSuperAdmin = 1;
        if (isSuperAdmin.equals(BaseContextHandler.getIsSuperAdmin())) {
            return true;
        }
        // 获取当前用户角色权限列表
        List<String> roles = BaseContextHandler.getRoles();
        if (BeanUtil.isEmpty(roles)) {
            log.info("userName：{},没有角色权限", roles);
            return false;
        }
        // 初始化缓存角色权限
        // TODO 存redis中获取缓存,临时存放在内存中
        if (BeanUtil.isEmpty(ROLE_BTN_AUTH)) {
            ROLE_BTN_AUTH.putAll(roleAuthorizationBiz.initRoleMenus());
        }
        // 角色权限列表
        List<RoleMenuAuthDTO> menuButtonList = new ArrayList<>(InitialCapacityConstant.INITIAL_256_NUMBER);
        for (String role : roles) {
            List<RoleMenuAuthDTO> buttonList = ROLE_BTN_AUTH.get(role);
            if (BeanUtil.isEmpty(buttonList)) {
                continue;
            }
            menuButtonList.addAll(buttonList);
        }
        // 判断当前地址是否匹配权限
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        for (RoleMenuAuthDTO roleMenuAuth : menuButtonList) {
            // 判断请求地址和请求方式是否一致
            if (method.equals(roleMenuAuth.getMethod())) {
                boolean isMatch = antPathMatcher.match(roleMenuAuth.getUrl(), requestUrl);
                if (isMatch) {
                    return true;
                }
            }
        }
        return false;
    }
}